#include <list>
#include <string>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <typeinfo>
using namespace std;

#include <nlohmann/json.hpp>

// for convenience
using json = nlohmann::json;

enum Format
{
    RESET,
    FONT_BLACK,
    FONT_RED,
    FONT_GREEN,
    FONT_YELLOW,
    FONT_WHITE,
    FONT_DEFAULT,
    BG_BLACK,
    BG_RED,
    BG_GREEN,
    BG_YELLOW,
    BG_WHITE,
    BG_DEFAULT,
};

const string format[] = {
    "\033[0m",
    "\033[30m",
    "\033[31m",
    "\033[32m",
    "\033[33m",
    "\033[37m",
    "\033[49m",
    "\033[40m",
    "\033[41m",
    "\033[42m",
    "\033[43m",
    "\033[47m",
    "\033[49m"
};

enum class fruit
{
    apple,
    banana,
    cherry,
};

void title(const string& title, Format fmt = FONT_YELLOW)
{
    const size_t width = 80;
    const size_t len = title.length();

    cout << endl;
    cout << format[fmt] << setfill('=');

    cout << setw((width - len) / 2 - 1) << "";
    cout << " " << title << " ";
    cout << setw((width - len - 1) / 2) << "";
    
    cout << format[RESET] << setfill(' ') << endl;
    cout << endl;
}

struct demo
{
    int i = 32;
    double d = 3.14159265;
    float f = 2.718281828f;
    string str = "hello";
};

void to_json(nlohmann::json& j, const demo& s) {
    j = {
        {"int" , s.i},
        {"double" , s.d},
        {"float" , s.f},
        {"string" , s.str}
    };
}

void from_json(const nlohmann::json& j, demo& s) {
    j.at("int").get_to(s.i);
    j.at("double").get_to(s.d);
    j.at("float").get_to(s.f);
    j.at("string").get_to(s.str);
}

int main()
{
    title("sample: Initialization List");
    json sample = {
        {"pi", 3.141},
        {"happy", true},
        {"name", "Niels"},
        {"nothing", nullptr},
        {"answer", {
            {"everything", 42}
        }},
        {"fruit", fruit::apple},
        {"list", {1, 0, 2}},
        {"object", {
            {"currency", "USD"},
            {"value", 42.99}
        }}
    };

    // the setw manipulator was overloaded to set the indentation for pretty printing
    title("Pretty setw(4)", FONT_GREEN);
    cout << setw(4) << sample << endl;

    title("sample: Write to pretty.json", FONT_GREEN);
    // write prettified JSON to another file
    std::ofstream ofs("pretty.json");
    if (!ofs.is_open()) {
        cout << "Can't open pretty.json!" << endl;
        return -1;
    }
    ofs << setw(8) << sample << endl;
    ofs.close();

    // read a JSON file
    title("Read from pretty.json", FONT_GREEN);
    std::ifstream ifs("pretty.json");
    if (!ifs.is_open()) {
        cout << "Can't open pretty.json!" << endl;
        return -1;
    }
    json j;
    ifs >> j;
    string json_str = j.dump();

    cout << j << endl;
    cout << json_str << endl;

    title("pretty.json: \"answer\"", FONT_GREEN);
    cout << j["answer"] << endl;
    int answer = j["answer"]["everything"];
    cout << answer << endl;

    title("sample.json: \"list\"");
    cout << j["list"] << endl;
    int num = j["list"][0];
    cout << num << endl;
    cout << format[FONT_RED] << "list[4]: " << format[RESET];
    cout << j["list"][4] << endl;
    auto temp = j["list"][4];
    cout << typeid(temp).name() << endl;

    // when key repeats, keep the first one
    json repeat = {
        {"id", 1},
        {"name", "Adam"},
        {"id", 2},
        {"sex", "male"},
        {"list", {0, 1, 1, 2}},
        {"list", {3, 4}}
    };

    title("repeat.json");
    cout << setw(4) << repeat << endl;
    cout << repeat["id"] << endl;
    cout << repeat["list"] << endl;

    json object = {
        {"name", "Adam"},
        {"sex", "male"},
        {"id", 1}
    };

    json objects;
    objects["objects"] = json::array({object, object, object});

    title("object.json");
    cout << setw(4) << object << endl;

    title("objects.json");
    cout << setw(4) << objects << endl;

    cout << "root:" << endl;
    cout << "size: " << objects.size() << endl;
    cout << "type: " << objects.type_name() << endl;
    cout << endl;

    cout << "root[objects]" << endl;
    cout << "size: " << objects["objects"].size() << endl;
    cout << "type: " << objects["objects"].type_name() << endl;

    cout << "contains" << endl;
    cout << objects.contains("object") << endl;
    cout << objects.contains("objects") << endl;

    objects["objects"] += object;
    cout << setw(4) << objects << endl;

    title("to_json and from_json");

    demo s;
    json js = s;
    cout << setw(4) << js << endl;

    demo t = js.get<demo>();
    cout << "t.i" << t.i << endl;

    for (auto o : objects["objects"]) {
        cout << o["id"] << endl;
    }
    return 0;
}